package fr.labri.macchiato.feature.localRepositoryJson;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.storage.client.Storage;

import fr.labri.macchiato.core.api.Repository;
import fr.labri.macchiato.core.technical.Encoder;
import fr.labri.macchiato.core.technical.FrameworkException;
import fr.labri.macchiato.core.technical.Identifiable;

/**
 * Repository storing entities in the browser's storage This repository can use
 * either local or session storage, depending on the argument passed as
 * constructor
 * 
 * @author Matthieu FOUCAULT
 * 
 * @param <T>
 * @param <Tid>
 */
public abstract class StorageRepository<T extends Identifiable<Tid>, Tid>
		implements Repository<T, Tid> {

	private static final String IDS = "_IDS";
	private Storage storage;

	/**
	 * As the browser's storage works as a String-String map, a prefix is needed
	 * before the id of an entity to allow two entities of different classes to
	 * have the same id
	 * 
	 * @return A prefix for the entities stored in this repository, which
	 *         corresponds to the type of the entity (This method could simply
	 *         return the class name)
	 */
	protected abstract String getKeyPrefix();

	/**
	 * GWT's {@link Storage}
	 */
	private Storage getStorage() {
		return this.storage;
	}

	/**
	 * Entities have to be encoded as String to be stored in the HTML5 Storage
	 * 
	 * @return an encoder
	 */
	protected abstract String getEncodedEntity(T entity);

	protected abstract T getDecodedEntity(String string);
	
	/**
	 * As there is no index in the HTML5 storage, we have to maintain a list of
	 * the stored ids
	 * 
	 * @return an encoder for the list of ids
	 */
	protected abstract Encoder<List<Tid>> getIdsEncoder();

	private List<Tid> getIds() {
		String encodedIds = getStorage().getItem(getKeyPrefix() + IDS);
		if (encodedIds == null)
			return null;

		return getIdsEncoder().decode(encodedIds);
	}

	private void setIds(List<Tid> ids) {
		getStorage().setItem(getKeyPrefix() + IDS, getIdsEncoder().encode(ids));

	}

	/**
	 * Initializes the repository, using either local or session storage
	 * 
	 * @param useLocalStorage
	 */
	public StorageRepository(boolean useLocalStorage) {
		if (useLocalStorage)
			storage = Storage.getLocalStorageIfSupported();
		else
			storage = Storage.getSessionStorageIfSupported();

		if (storage == null)
			throw new FrameworkException("HTML5 Storage is not supported!");
	}

	public void save(T entity) {
		List<Tid> ids = getIds();
		if (ids == null)
			ids = new ArrayList<Tid>();
		if (!ids.contains(entity.getId())) {
			ids.add(entity.getId());
			setIds(ids);
		}

		String encodedEntity = getEncodedEntity(entity);
		String key = getKeyPrefix() + entity.getId();
		// FIXME? This works perfectly with a String id, but we may have
		// problems if we use a more complex object
		getStorage().setItem(key, encodedEntity);
	}

	public T load(Tid id) {
		String encodedEntity = getStorage().getItem(getKeyPrefix() + id);
		if (encodedEntity == null)
			return null;
		return getDecodedEntity(encodedEntity);
	}

	public List<T> loadAll() {
		List<T> entities = new ArrayList<T>();
		List<Tid> ids = getIds();
		if (ids != null)
			for (Tid id : ids)
				entities.add(load(id));
		return entities;
	}

	public void remove(Tid id) {
		List<Tid> ids = getIds();
		if (ids != null && ids.contains(id)) {
			getStorage().removeItem(getKeyPrefix() + id);
			ids.remove(id);
			setIds(ids);
		}

	}

	public void clean() {
		List<Tid> ids = getIds();
		for (Tid id : ids) {
			remove(id);
		}
	}

	public boolean isEmpty() {
		List<Tid> ids = getIds();
		return ids == null || ids.isEmpty();
	}

}
